package tjs.styudy.mini.springboot.devtools.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.CachedIntrospectionResults;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.system.JavaVersion;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import tjs.styudy.mini.springboot.devtools.fileWatch.ClassPathChangedEvent;

import java.beans.Introspector;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;

public class Restarter {
    private static final Logger logger = LoggerFactory.getLogger(Restarter.class);

    private static final Object INSTANCE_MONITOR = new Object();

    private static Restarter instance;

    private final String mainClassName;

    private final ClassLoader applicationClassLoader;

    private final String[] args;


    private final List<ConfigurableApplicationContext> rootContexts = new CopyOnWriteArrayList<>();

    private String initialUrls;

    public Restarter(Thread thread, String[] args) {
        this.mainClassName = getMainClassName(thread);
        this.applicationClassLoader = thread.getContextClassLoader();
        this.args = args;
        this.initialUrls = Thread.currentThread().getContextClassLoader().getResource("").getPath();
    }

    public String getInitialUrls() {
        return initialUrls;
    }

    public static Restarter getInstance() {
        synchronized (INSTANCE_MONITOR) {
            Assert.state(instance != null, "Restarter has not been initialized");
            return instance;
        }
    }

    public void restart(ClassPathChangedEvent classPathChangedEvent) {
        this.immediateRestart();
    }

    private void immediateRestart() {
        try {
            this.doStart();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        /**
         * 抛出异常，结束上一个线程（首次启动时那个线程只执行到
         * {@link SpringApplication#run(java.lang.String...)}
         *      (listeners.starting(bootstrapContext, this.mainApplicationClass);//发布启动事件后，由于这里报错而结束了)
         *  如果不结束这条线程，将会二次初始化IOC等
         */
        SilentExitExceptionHandler.exitCurrentThread("上一个线程结束。。。");
    }

    private void doStart() throws InterruptedException {
        RestartClassLoader restartClassLoader = new RestartClassLoader(this.initialUrls);
        Thread.UncaughtExceptionHandler uncaughtExceptionHandler = SilentExitExceptionHandler.extendThreadOldHandler(Thread.currentThread());
        RestartLauncher restartLauncher = new RestartLauncher(restartClassLoader, uncaughtExceptionHandler, this.mainClassName, this.args);
        this.stop();
        restartLauncher.start();
        restartLauncher.join();
    }

    protected void stop() {
        // 停掉旧的应用
        for (ConfigurableApplicationContext rootContext : this.rootContexts) {
            rootContext.close();
            this.rootContexts.remove(rootContext);
        }
        // 清理一些由AppClassloader或其他父加载器加载的Class的静态常量
        CopyMethod.cleanupCaches();

        System.gc();
        System.runFinalization();
    }

    void prepare(ConfigurableApplicationContext applicationContext) {
        this.rootContexts.add(applicationContext);
    }

    public static void initialize(String[] args) {
        Restarter localInstance = null;
        synchronized (INSTANCE_MONITOR) {
            if (instance == null) {// instance是AppClassLoader加载的
                localInstance = new Restarter(Thread.currentThread(), args);
                instance = localInstance;
            }
        }
        if (localInstance != null) {
            // 重启main方法
            localInstance.immediateRestart();
        }
    }

    private static String getMainClassName(Thread thread) {
        try {
            for (StackTraceElement element : thread.getStackTrace()) {
                if ("main".equals(element.getMethodName())) {
                    Class<?> elementClass = Class.forName(element.getClassName());
                    Method method = elementClass.getDeclaredMethod("main", String[].class);
                    if (Modifier.isStatic(method.getModifiers())) {
                        return method.getDeclaringClass().getName();
                    }
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new RuntimeException("未找到启动类");
        }
        throw new RuntimeException("未找到启动类");
    }

    static class CopyMethod {
        private static final Logger logger = LoggerFactory.getLogger(CopyMethod.class);

        private static void cleanupCaches() {
            try {
                Introspector.flushCaches();
                cleanupKnownCaches();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        private static void cleanupKnownCaches() throws Exception {
            // Whilst not strictly necessary it helps to cleanup soft reference caches
            // early rather than waiting for memory limits to be reached
            ResolvableType.clearCache();
            cleanCachedIntrospectionResultsCache();
            ReflectionUtils.clearCache();
            clearAnnotationUtilsCache();
            if (!JavaVersion.getJavaVersion().isEqualOrNewerThan(JavaVersion.NINE)) {
                clear("com.sun.naming.internal.ResourceManager", "propertiesCache");
            }
        }

        private static void cleanCachedIntrospectionResultsCache() throws Exception {
            CachedIntrospectionResults.acceptClassLoader(CachedIntrospectionResults.class.getClassLoader());
//        clear(CachedIntrospectionResults.class, "acceptedClassLoaders");
//        clear(CachedIntrospectionResults.class, "strongClassCache");//fixme 清除这个缓存，@RestController才能重新加载
//        clear(CachedIntrospectionResults.class, "softClassCache");
        }

        private static void clearAnnotationUtilsCache() throws Exception {
            try {
                AnnotationUtils.clearCache();
            } catch (Throwable ex) {
                clear(AnnotationUtils.class, "findAnnotationCache");
                clear(AnnotationUtils.class, "annotatedInterfaceCache");
            }
        }

        private static void clear(String className, String fieldName) {
            try {
                clear(Class.forName(className), fieldName);
            } catch (Exception ex) {
                if (CopyMethod.logger.isDebugEnabled()) {
                    CopyMethod.logger.debug("Unable to clear field " + className + " " + fieldName, ex);
                }
            }
        }

        private static void clear(Class<?> type, String fieldName) throws Exception {
            try {
                Field field = type.getDeclaredField(fieldName);
                field.setAccessible(true);
                Object instance = field.get(null);
                if (instance instanceof Set) {
                    ((Set<?>) instance).clear();
                }
                if (instance instanceof Map) {
                    ((Map<?, ?>) instance).keySet().removeIf(CopyMethod::isFromRestartClassLoader);
                }
            } catch (Exception ex) {
                if (CopyMethod.logger.isDebugEnabled()) {
                    CopyMethod.logger.debug("Unable to clear field " + type + " " + fieldName, ex);
                }
            }
        }

        private static boolean isFromRestartClassLoader(Object object) {
            return (object instanceof Class && ((Class<?>) object).getClassLoader() instanceof RestartClassLoader);
        }
    }
}